package com.titanrise.crm.service.impl;

import com.titanrise.crm.common.enums.LogEffect;
import com.titanrise.crm.common.enums.TradeStatusEnum;
import com.titanrise.crm.common.utils.*;
import com.titanrise.crm.dao.SalespersonDao;
import com.titanrise.crm.domain.dto.CustomerDto;
import com.titanrise.crm.domain.dto.MessageDto;
import com.titanrise.crm.domain.dto.TradeDto;
import com.titanrise.crm.domain.model.*;
import com.titanrise.crm.domain.model.enums.MessageStatus;
import com.titanrise.crm.domain.vo.CustomerVo;
import com.titanrise.crm.domain.vo.MessageVo;
import com.titanrise.crm.domain.vo.TradeVo;
import com.titanrise.crm.environment.annotation.Autowired;
import com.titanrise.crm.environment.annotation.Service;
import com.titanrise.crm.environment.menu.MenuContext;
import com.titanrise.crm.environment.menu.action.Action;
import com.titanrise.crm.service.SalespersonService;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.function.Function;

@Service
public class SalespersonServiceImpl implements SalespersonService {
    @Autowired
    private SalespersonDao salespersonDao;

    @Override
    public Action beforeEnter(MenuContext context) {
        return Functions.pipe(
                (Role) context.getState().get("CurrentRole"),
                Maybe::fromNullable,
                MaybePipeUtil.flatMap(Functions.flow(Role::getUid, salespersonDao::findSalespersonByUserID)),
                MaybePipeUtil.map(Functions.flow(
                        Functions.forEach((currentSalesperson) -> context.getState().put("CurrentSalesperson", currentSalesperson)),
                        salesperson -> Pair.of(
                                salespersonDao.findTradesBySalespersonUidAndStatus(salesperson.getUserId(), TradeStatusEnum.PAID),
                                salespersonDao.findMessagesByReceiverID(salesperson.getUserId())
                        ),
                        (pair) ->
                                Pair.of(Functions.pipe(
                                                pair.getFirst(),
                                                ListPipeUtil.filter(e -> e.getStatus().equals(TradeStatusEnum.PAID.getStatus())),
                                                List::size),
                                        Functions.pipe(
                                                pair.getSecond(),
                                                ListPipeUtil.filter(e -> e.getStatus().equals(MessageStatus.UNREAD)),
                                                List::size
                                        )),
                        (countPair) -> Functions.pipe(
                                countPair.getFirst() > 0
                                        ? Console.putStrLn("当前有" + countPair.getFirst() + "条待处理订单！", LogEffect.BLUE)
                                        : SupplierPipeUtil.emptySupplier,
                                SupplierPipeUtil.andThen(
                                        countPair.getSecond() > 0
                                                ? Console.putStrLn("当前有" + countPair.getSecond() + "条未读消息！", LogEffect.BLUE)
                                                : SupplierPipeUtil.emptySupplier
                                ),
                                SupplierPipeUtil.andThen(() -> (Action) null)
                        )
                )),
                MaybePipeUtil.orElse(Functions.pipe(
                        Console.putStrLn("登录信息错误！请重新登录！", LogEffect.RED),
                        SupplierPipeUtil.andThen(() -> Action.JUMP_TO_MENU("/"))
                )),
                Supplier::get
        );
    }

    @Override
    public void beforeLeave(MenuContext context) {
        String logId = (String) context.getState().get("CurrentLoginLogID");
        salespersonDao.updateLoginLogLogoutTime(logId);
        context.getState().remove("CurrentRole");
        context.getState().remove("CurrentSalesperson");
        context.getState().remove("CurrentLoginLogID");
    }

    @Override
    public void showMessages(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        List<MessageDto> messages = salespersonDao.findMessagesByReceiverID(currentSalesperson.getUserId());
        Functions.pipe(
                Maybe.fromList(messages),
                MaybePipeUtil.map(
                        ListPipeUtil.mapWithIndex(
                                (msg, index) -> MessageVo.builder()
                                        .senderName(msg.getSenderName())
                                        .content(msg.getContent())
                                        .sendTime(Maybe.fromNullable(msg.getCreateTime()).map(e -> e.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse(null))
                                        .status(msg.getStatus().getMessage())
                                        .build()
                        )
                ),
                MaybePipeUtil.map(
                        ListPipeUtil.toSorted(
                                (msg1, msg2) ->
                                        msg1.getStatus().equals(MessageStatus.UNREAD.getMessage())
                                                && msg2.getStatus().equals(MessageStatus.READ.getMessage())
                                                ? -1
                                                : msg1.getStatus().equals(msg2.getStatus())
                                                ? 0 : 1
                        )
                ),
                MaybePipeUtil.match(
                        (msgs) -> {
                            Console.showTable(msgs);
                            return messages;
                        },
                        () -> {
                            Console.println("暂无消息……");
                            return messages;
                        }
                ),
                ListPipeUtil.forEach((msg) -> {
                    if (msg.getStatus().equals(MessageStatus.UNREAD))
                        salespersonDao.updateMessageById(
                                Messages.builder()
                                        .messageId(msg.getId())
                                        .senderId(msg.getSenderId())
                                        .receiverId(msg.getReceiverId())
                                        .content(msg.getContent())
                                        .status(MessageStatus.READ)
                                        .createTime(msg.getCreateTime())
                                        .readTime(LocalDateTime.now())
                                        .build()
                        );
                })
        );
    }

    @Override
    public void showUnhandledTrades(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        List<TradeDto> trades = salespersonDao.findTradeDtoBySalespersonID(currentSalesperson.getUserId());
        Functions.pipe(
                trades,
                ListPipeUtil.filter(e -> e.getStatus().equals(TradeStatusEnum.PAID.getStatus())),
                Maybe::fromList,
                MaybePipeUtil.map(
                        ListPipeUtil.mapWithIndex((trade, index) ->
                                TradeVo.builder()
                                        .id(index + 1)
                                        .number(trade.getNumber())
                                        .note(trade.getNote())
                                        .status(TradeStatusEnum.valueOf(trade.getStatus()).getMsg())
                                        .finishDate(Maybe.fromNullable(trade.getFinishDate()).map(e -> e.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse(null))
                                        .updateTime(Maybe.fromNullable(trade.getUpdateTime()).map(e -> e.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse(null))
                                        .customerName(trade.getClientName())
                                        .goodsName(trade.getGoodsName())
                                        .price(trade.getPrice())
                                        .build()
                        )
                ),
                MaybePipeUtil.match(
                        (tradeVos) -> {
                            Console.showTable(tradeVos);
                            String tradeId = ProjectUtil.getLine("请选择要处理的订单：", (line) -> {
                                try {
                                    int i = Integer.parseInt(line.trim());
                                    return i < 1 || i > trades.size()
                                            ? Either.left("输入范围错误！请重试！")
                                            : Either.right(trades.get(i - 1).getTradeId());
                                } catch (NumberFormatException e) {
                                    return Either.right("输入格式错误！请重试！");
                                }
                            });
                            salespersonDao.updateStatusByTradeID(tradeId, TradeStatusEnum.CONFIRM_DEAL);
                            Console.println("更新完成！");
                            return Nothing.value;
                        },
                        () -> {
                            Console.println("暂无请求需要处理……");
                            return Nothing.value;
                        }
                )
        );
    }

    @Override
    public void showBelongedCustomer(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        List<CustomerDto> customers = salespersonDao.findBelongedCustomer(currentSalesperson.getUserId());
        Functions.pipe(
                Maybe.fromList(customers),
                MaybePipeUtil.map(ListPipeUtil.mapWithIndex(
                        (customer, index) -> CustomerVo.builder()
                                .id(index + 1)
                                .email(customer.getEmail())
                                .name(customer.getName())
                                .address(customer.getCountry() + customer.getProvince() + customer.getCity() + customer.getDistrict())
                                .annualIncome(customer.getAnnualIncome())
                                .description(customer.getDescription())
                                .industry(customer.getIndustry())
                                .postCode(customer.getPostCode())
                                .phoneNumber(customer.getPhoneNumber())
                                .build()
                )),
                MaybePipeUtil.match(
                        Functions.ofConsumer(Console::showTable),
                        Console.putStrLn("暂无所属客户……")
                )
        );
    }

    @Override
    public void showNonBelongingCustomerAndSelect(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        List<CustomerDto> customers = salespersonDao.findNonBelongingCustomer();

        Functions.pipe(
                Maybe.<CustomerDto>fromList(customers),
                MaybePipeUtil.<List<CustomerDto>, List<CustomerVo>>map(ListPipeUtil.mapWithIndex(
                        (customer, index) -> CustomerVo.builder()
                                .id(index + 1)
                                .email(customer.getEmail())
                                .name(customer.getName())
                                .address(customer.getCountry() + customer.getProvince() + customer.getCity() + customer.getDistrict())
                                .annualIncome(customer.getAnnualIncome())
                                .description(customer.getDescription())
                                .industry(customer.getIndustry())
                                .postCode(customer.getPostCode())
                                .phoneNumber(customer.getPhoneNumber())
                                .build()
                )),
                MaybePipeUtil.<List<CustomerVo>, Maybe<Nothing>>match(
                        Functions.flow(
                                Functions.ofConsumer(Console::showTable),
                                (ignored) -> Maybe.just(Nothing.value)
                        ),
                        Functions.pipe(
                                Console.putStrLn("暂无无所属客户……"),
                                SupplierPipeUtil.andThen(Maybe::nothing)
                        )
                ),
                MaybePipeUtil.<Nothing, Integer>andThen(() -> ProjectUtil.getLine("请选择要添加的客户（输入q则不添加）：", Functions.flow(
                        ValidationUtil::trimThenRight,
                        EitherPipeUtil.flatMap(line ->
                                line.equals("q")
                                        ? Either.right(Maybe.nothing())
                                        : Functions.pipe(
                                        Either.right(line),
                                        ValidationUtil.nonEmpty("输入不可为空！请重试！"),
                                        ValidationUtil.parseInt("输入格式错误！请重试！"),
                                        ValidationUtil.greaterThan("输入值必须大于0！请重试！", 0),
                                        ValidationUtil.lessThan("输入值必须小于" + (customers.size() + 1) + "！请重试！", customers.size() + 1),
                                        EitherPipeUtil.map(Maybe::just)
                                )
                        )
                ))),
                MaybePipeUtil.<Integer>forEach(index -> salespersonDao.addRelation(currentSalesperson.getUserId(), customers.get(index - 1).getId()))
        );
    }

    @Override
    public void showConfirmedTrades(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        List<TradeDto> trades = salespersonDao.findTradeDtoBySalespersonID(currentSalesperson.getUserId());
        Functions.pipe(
                trades,
                ListPipeUtil.filter(e -> e.getStatus().equals(TradeStatusEnum.CONFIRM_DEAL.getStatus())),
                Maybe::fromList,
                MaybePipeUtil.map(
                        ListPipeUtil.mapWithIndex((trade, index) ->
                                TradeVo.builder()
                                        .id(index + 1)
                                        .number(trade.getNumber())
                                        .note(trade.getNote())
                                        .status(TradeStatusEnum.valueOf(trade.getStatus()).getMsg())
                                        .finishDate(Maybe.fromNullable(trade.getFinishDate()).map(e -> e.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse(null))
                                        .updateTime(Maybe.fromNullable(trade.getUpdateTime()).map(e -> e.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse(null))
                                        .customerName(trade.getClientName())
                                        .goodsName(trade.getGoodsName())
                                        .price(trade.getPrice())
                                        .build()
                        )
                ),
                MaybePipeUtil.match(
                        Functions.ofConsumer(Console::showTable),
                        Console.putStrLn("暂无订单信息……")
                )
        );
    }

    @Override
    public void showWorkResults(MenuContext context) {
        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");

        List<Trade> trades = Functions.pipe(
                currentSalesperson,
                Salesperson::getUserId,
                uid -> Pair.of(uid, TradeStatusEnum.PAID),
                Functions.ofBiFunction(salespersonDao::findTradesBySalespersonUidAndStatus)
        );

        Supplier<Nothing> result1 = Functions.pipe(
                trades,
                ListPipeUtil.map((trade) -> trade.getPrice().multiply(BigDecimal.valueOf(trade.getNumber()))),
                ListPipeUtil.foldLeft(BigDecimal::add, BigDecimal.valueOf(0)),
                n -> Console.putStrLn("总销售额：" + n)
        );

        Supplier<Nothing> result2 = Functions.pipe(
                trades,
                List::size,
                n -> Console.putStrLn("总订单数：" + n)
        );

        Supplier<Nothing> result3 = Functions.pipe(
                currentSalesperson,
                Salesperson::getUserId,
                salespersonDao::findBelongedCustomer,
                List::size,
                n -> Console.putStrLn("总客户数：" + n)
        );

        Functions.pipe(
                SupplierPipeUtil.emptySupplier,
                SupplierPipeUtil.andThen(result1),
                SupplierPipeUtil.andThen(result2),
                SupplierPipeUtil.andThen(result3),
                Supplier::get
        );
    }

    @Override
    public void modifyPersonalInformation(MenuContext context) {
        BiFunction<String, Integer, String> repeat = (s, n) -> {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++)
                sb.append(s);
            return sb.toString();
        };

        Function<String, Either<String, String>> baseValidator = Functions.flow(
                ValidationUtil::trimThenRight,
                ValidationUtil.nonEmpty("输入不可为空！请重试！")
        );

        Salesperson currentSalesperson = (Salesperson) context.getState().get("CurrentSalesperson");
        Role currentRole = (Role) context.getState().get("CurrentRole");
        User currentUser = (User) context.getState().get("CurrentUser");

        Console.println("1. 用户名：" + currentRole.getUsername());
        Console.println("2. 密码：" + repeat.apply("*", currentRole.getPassword().length()));
        Console.println("3. 姓名：" + currentRole.getName());
        Console.println("4. 手机号：" + currentSalesperson.getPhoneNumber());
        Console.println("5. 邮箱：" + currentRole.getEmail());

        Maybe<Integer> choice = ProjectUtil.getLine("请选择要修改的项（输入q则退出）：", Functions.flow(
                baseValidator,
                ValidationUtil.quitWhen(line -> line.equals("q"), Functions.flow(
                        ValidationUtil.parseInt("输入格式错误！请重试！"),
                        ValidationUtil.lessThan("输入值不可大于5！请重试！", 6),
                        ValidationUtil.greaterThan("输入值不可小于1！请重试！", 0)
                ))
        ));

        choice.ifPresent((i) -> {
            switch (i) {
                case 1:
                    Functions.pipe(
                            ProjectUtil.getLine("请输入新用户名（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(line -> line.equals("q"), Functions.flow(
                                            ValidationUtil.lenLowerLimit(2, "用户名长度不可小于2！请重试！"),
                                            ValidationUtil.lenUpperLimit(12, "用户名长度不可大于12！请重试！"),
                                            ValidationUtil.matchRegex("^[a-zA-Z0-9_\\u4E00-\\u9FFF]+$", "用户名仅能包含字母、数字、汉字以及下划线！请重试！"),
                                            ValidationUtil.validate((username) -> !salespersonDao.existsUsername(username), "用户名已存在！请重试！")
                                    ))
                            )),
                            MaybePipeUtil.forEach(newUsername -> {
                                currentRole.setUsername(newUsername);
                                salespersonDao.updateUsernameByRoleId(currentRole.getId(), currentRole.getUsername());
                                Console.println("修改完成！");
                            })
                    );
                    break;
                case 2:
                    Functions.pipe(
                            ProjectUtil.getLine("请确认密码（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(
                                            line -> line.equals("q"),
                                            ValidationUtil.equals(currentRole.getPassword(), "密码错误！请重试！")
                                    )
                            )),
                            MaybePipeUtil.andThen(() -> ProjectUtil.getLine("请输入新密码（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(line -> line.equals("q"), Functions.flow(
                                            ValidationUtil.lenLowerLimit(6, "密码长度不可小于6！请重试！"),
                                            ValidationUtil.lenUpperLimit(12, "密码长度不可大于12！请重试！"),
                                            ValidationUtil.matchRegex("^[a-zA-Z0-9@#\\$%&!\\*\\?]+$", "密码仅能包含字母、数字以及特殊字符（@, #, $, %, &, !, *, ?）！请重试！")
                                    ))
                            ))),
                            MaybePipeUtil.forEach((newPassword) -> {
                                currentRole.setPassword(newPassword);
                                salespersonDao.updatePasswordByRoleId(currentRole.getId(), currentRole.getPassword());
                                Console.println("修改完成！");
                            })
                    );
                    break;
                case 3:
                    Functions.pipe(
                            ProjectUtil.getLine("请输入新姓名（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(line -> line.equals("q"), Functions.flow(
                                            ValidationUtil.lenLowerLimit(2, "姓名长度不可小于2！请重试！"),
                                            ValidationUtil.lenUpperLimit(5, "姓名长度不可大于5！请重试！"),
                                            ValidationUtil.matchRegex("^[\\u4E00-\\u9FFF]+$", "姓名只能输入中文！请重试！")
                                    ))
                            )),
                            MaybePipeUtil.forEach(newName -> {
                                currentRole.setName(newName);
                                salespersonDao.updateNameById(currentRole.getId(), currentRole.getName());
                                Console.println("修改完成！");
                            })
                    );
                    break;
                case 4:
                    Functions.pipe(
                            ProjectUtil.getLine("请输入新手机号（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(
                                            line -> line.equals("q"),
                                            ValidationUtil.matchRegex("^1[3-9]\\d{9}$", "输入格式错误！请重试！")
                                    )
                            )),
                            MaybePipeUtil.forEach(newPhoneNumber -> {
                                currentUser.setPhoneNumber(newPhoneNumber);
                                currentSalesperson.setPhoneNumber(newPhoneNumber);
                                salespersonDao.updatePhoneNumberByUid(currentUser.getId(), newPhoneNumber);
                                Console.println("修改完成！");
                            })
                    );
                    break;
                case 5:
                    Functions.pipe(
                            ProjectUtil.getLine("请输入新邮箱（输入q则退出）：", Functions.flow(
                                    baseValidator,
                                    ValidationUtil.quitWhen(
                                            line -> line.equals("q"),
                                            ValidationUtil.matchRegex("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z]{2,}$", "输入格式错误！请重试！")
                                    )
                            )),
                            MaybePipeUtil.forEach(newEmail -> {
                                currentRole.setEmail(newEmail);
                                salespersonDao.updateEmailById(currentRole.getId(), currentRole.getEmail());
                                Console.println("修改完成！");
                            })
                    );
                    break;
            }
        });
    }
}
